/* 
 * %Z%file      %M% 
 * %Z%author    Sun Microsystems, Inc. 
 * %Z%version   %I% 
 * %Z%date      %D% 
 * 
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */ 

package com.sun.jmx.snmp.agent;


// java imports
//
import java.util.Vector;
import java.util.Enumeration;
import java.util.Iterator;

// jmx imports
//
import javax.management.AttributeList;
import javax.management.Attribute;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.InstanceNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.RuntimeOperationsException;
import com.sun.jmx.snmp.SnmpOid;
import com.sun.jmx.snmp.SnmpValue;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpStatusException;


/**
 * <p>
 * This class is a utility class that transforms SNMP GET / SET requests
 * into standard JMX getAttributes() setAttributes() requests.
 * </p>
 *
 * <p>
 * The transformation relies on the metadata information provided by the
 * {@link com.sun.jmx.snmp.agent.SnmpGenericMetaServer} object which is
 * passed as the first parameter to every method. This SnmpGenericMetaServer
 * object is usually a Metadata object generated by <code>mibgen</code>.
 * </p>
 *
 * <p><b><i>
 * This class is used internally by mibgen generated metadata objects and
 * you should never need to use it directly.
 * </b></i></p>
 * <p><b>This API is a Sun Microsystems internal API  and is subject 
 * to change without notice.</b></p>
 **/

public class SnmpGenericObjectServer {

    // ----------------------------------------------------------------------
    // 
    //    Protected variables
    //
    // ----------------------------------------------------------------------

    /**
     * The MBean server through which the MBeans will be accessed.
     **/
    protected final MBeanServer server;

    // ----------------------------------------------------------------------
    //
    // Constructors
    //
    // ----------------------------------------------------------------------

    /**
     * Builds a new SnmpGenericObjectServer. Usually there will be a single
     * object of this type per MIB.
     *
     * @param server The MBeanServer in which the MBean accessed by this
     *               MIB are registered.
     **/
    public SnmpGenericObjectServer(MBeanServer server) {
	this.server = server;
    }

    /**
     * Execute an SNMP GET request. 
     *
     * <p>
     * This method first builds the list of attributes that need to be
     * retrieved from the MBean and then calls getAttributes() on the
     * MBean server. Then it updates the SnmpMibSubRequest with the values
     * retrieved from the MBean.
     * </p>
     * 
     * <p>
     * The SNMP metadata information is obtained through the given
     * <code>meta</code> object, which usually is an instance of a
     * <code>mibgen</code> generated class.
     * </p>
     *
     * <p><b><i>
     * This method is called internally by <code>mibgen</code> generated
     * objects and you should never need to call it directly.
     * </i></b></p>
     *
     * @param meta  The metadata object impacted by the subrequest
     * @param name  The ObjectName of the MBean impacted by this subrequest
     * @param req   The SNMP subrequest to execute on the MBean
     * @param depth The depth of the SNMP object in the OID tree.
     *
     * @exception SnmpStatusException whenever an SNMP exception must be 
     *      raised. Raising an exception will abort the request.<br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerGetException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     **/            
    public void get(SnmpGenericMetaServer meta, ObjectName name, 
		    SnmpMibSubRequest req, int depth) 
	throws SnmpStatusException {

	// java.lang.System.out.println(">>>>>>>>> GET " + name);

	final int           size     = req.getSize();
	final Object        data     = req.getUserData();
	final String[]      nameList = new String[size];
	final SnmpVarBind[] varList  = new SnmpVarBind[size];
	final long[]        idList   = new long[size];
	int   i = 0;

	for (Enumeration e=req.getElements(); e.hasMoreElements();) {
            final SnmpVarBind var= (SnmpVarBind) e.nextElement(); 
	    try {
		final long id = var.oid.getOidArc(depth);
		nameList[i]   = meta.getAttributeName(id);
		varList[i]    = var;
		idList[i]     = id;

		// Check the access rights according to the MIB.
		// The MBean might be less restrictive (have a getter
		// while the MIB defines the variable as AFN)
		//
		meta.checkGetAccess(id,data);

		//java.lang.System.out.println(nameList[i] + " added.");
		i++;
            } catch(SnmpStatusException x) {
		//java.lang.System.out.println("exception for " + nameList[i]);
		//x.printStackTrace();
		req.registerGetException(var,x);
	    }
	}

	AttributeList result = null;
	int errorCode = SnmpStatusException.noSuchInstance;

	try {
	    result = server.getAttributes(name,nameList);
	} catch (InstanceNotFoundException f) {
	    //java.lang.System.out.println(name + ": instance not found.");
	    //f.printStackTrace();
	    result = new AttributeList();
	} catch (ReflectionException r) {
	    //java.lang.System.out.println(name + ": reflexion error.");
	    //r.printStackTrace();
	    result = new AttributeList();
	} catch (Exception x) {
	    result = new AttributeList();
	}


	final Iterator it = result.iterator();
	
	for (int j=0; j < i; j++) {
	    if (!it.hasNext()) {
		//java.lang.System.out.println(name + "variable[" + j + 
		//			     "] absent");
		final SnmpStatusException x =
		    new SnmpStatusException(errorCode);
		req.registerGetException(varList[j],x);
		continue;
	    }

	    final Attribute att = (Attribute) it.next();

	    while ((j < i) && (! nameList[j].equals(att.getName()))) {
		//java.lang.System.out.println(name + "variable[" +j + 
		//			     "] not found");
		final SnmpStatusException x =
		    new SnmpStatusException(errorCode);
		req.registerGetException(varList[j],x);
		j++;
	    }

	    if ( j == i) break;
	    
	    try {
		varList[j].value = 
		    meta.buildSnmpValue(idList[j],att.getValue());
	    } catch (SnmpStatusException x) {
		req.registerGetException(varList[j],x);
	    }
	    //java.lang.System.out.println(att.getName() + " retrieved.");
	}
	//java.lang.System.out.println(">>>>>>>>> END GET");
    }

    /**
     * Get the value of an SNMP variable.
     *
     * <p><b><i>
     * You should never need to use this method directly.
     * </i></b></p>
     *
     * @param meta  The impacted metadata object
     * @param name  The ObjectName of the impacted MBean
     * @param id    The OID arc identifying the variable we're trying to set.
     * @param data  User contextual data allocated through the
     *        {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}
     *
     * @return The value of the variable.
     *
     * @exception SnmpStatusException whenever an SNMP exception must be 
     *      raised. Raising an exception will abort the request. <br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerGetException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     **/            
    public SnmpValue get(SnmpGenericMetaServer meta, ObjectName name, 
			 long id, Object data) 
	throws SnmpStatusException {
	final String attname = meta.getAttributeName(id);
	Object result = null;

	try {
	    result = server.getAttribute(name,attname);
	} catch (MBeanException m) {
	    Exception t = m.getTargetException();
	    if (t instanceof SnmpStatusException) 
		throw (SnmpStatusException) t;
	    throw new SnmpStatusException(SnmpStatusException.noSuchInstance);
	} catch (Exception e) {
	    throw new SnmpStatusException(SnmpStatusException.noSuchInstance);
	}

	return meta.buildSnmpValue(id,result);
    }

    /**
     * Execute an SNMP SET request. 
     *
     * <p>
     * This method first builds the list of attributes that need to be
     * set on the MBean and then calls setAttributes() on the
     * MBean server. Then it updates the SnmpMibSubRequest with the new 
     * values retrieved from the MBean.
     * </p>
     * 
     * <p>
     * The SNMP metadata information is obtained through the given
     * <code>meta</code> object, which usually is an instance of a
     * <code>mibgen</code> generated class.
     * </p>
     *
     * <p><b><i>
     * This method is called internally by <code>mibgen</code> generated
     * objects and you should never need to call it directly.
     * </i></b></p>
     *
     * @param meta  The metadata object impacted by the subrequest
     * @param name  The ObjectName of the MBean impacted by this subrequest
     * @param req   The SNMP subrequest to execute on the MBean
     * @param depth The depth of the SNMP object in the OID tree.
     *
     * @exception SnmpStatusException whenever an SNMP exception must be 
     *      raised. Raising an exception will abort the request. <br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerGetException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     **/            
    public void set(SnmpGenericMetaServer meta, ObjectName name, 
		    SnmpMibSubRequest req, int depth) 
	throws SnmpStatusException {

	final int size               = req.getSize();
	final AttributeList attList  = new AttributeList(size);
	final String[]      nameList = new String[size];
	final SnmpVarBind[] varList  = new SnmpVarBind[size];
	final long[]        idList   = new long[size];
	int   i = 0;

	for (Enumeration e=req.getElements(); e.hasMoreElements();) {
            final SnmpVarBind var= (SnmpVarBind) e.nextElement(); 
	    try {
		final long id = var.oid.getOidArc(depth);
		final String attname = meta.getAttributeName(id);
		final Object attvalue= 
		    meta.buildAttributeValue(id,var.value);
		final Attribute att = new Attribute(attname,attvalue);
		attList.add(att);
		nameList[i]   = attname;
		varList[i]    = var;
		idList[i]     = id;
		i++;
            } catch(SnmpStatusException x) {
		req.registerSetException(var,x);
	    }
	}

	AttributeList result = null;
	int errorCode = SnmpStatusException.noAccess;

	try {
	    result = server.setAttributes(name,attList);
	} catch (InstanceNotFoundException f) {
	    result = new AttributeList();
	    errorCode = SnmpStatusException.snmpRspInconsistentName;
	} catch (ReflectionException r) {
	    errorCode = SnmpStatusException.snmpRspInconsistentName;
	    result = new AttributeList();
	} catch (Exception x) {
	    result = new AttributeList();
	}

	final Iterator it = result.iterator();
	
	for (int j=0; j < i; j++) {
	    if (!it.hasNext()) {
		final SnmpStatusException x =
		    new SnmpStatusException(errorCode);
		req.registerSetException(varList[j],x);
		continue;
	    }

	    final Attribute att = (Attribute) it.next();

	    while ((j < i) && (! nameList[j].equals(att.getName()))) {
		final SnmpStatusException x =
		    new SnmpStatusException(SnmpStatusException.noAccess);
		req.registerSetException(varList[j],x);
		j++;
	    }

	    if ( j == i) break;
	    
	    try {
		varList[j].value = 
		    meta.buildSnmpValue(idList[j],att.getValue());
	    } catch (SnmpStatusException x) {
		req.registerSetException(varList[j],x);
	    }
	    
	}
    }

    /**
     * Set the value of an SNMP variable.
     *
     * <p><b><i>
     * You should never need to use this method directly.
     * </i></b></p>
     *
     * @param meta  The impacted metadata object
     * @param name  The ObjectName of the impacted MBean
     * @param x     The new requested SnmpValue
     * @param id    The OID arc identifying the variable we're trying to set.
     * @param data  User contextual data allocated through the
     *        {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}
     *
     * @return The new value of the variable after the operation.
     *
     * @exception SnmpStatusException whenever an SNMP exception must be 
     *      raised. Raising an exception will abort the request. <br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerSetException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     **/            
    public SnmpValue set(SnmpGenericMetaServer meta, ObjectName name, 
			 SnmpValue x, long id, Object data) 
	throws SnmpStatusException {
	final String attname = meta.getAttributeName(id);
	final Object attvalue= 
	    meta.buildAttributeValue(id,x);
	final Attribute att = new Attribute(attname,attvalue);

	Object result = null;

	try {
	    server.setAttribute(name,att);
	    result = server.getAttribute(name,attname);
	} catch(InvalidAttributeValueException iv) {
	    throw new 
		SnmpStatusException(SnmpStatusException.snmpRspWrongValue);
	} catch (InstanceNotFoundException f) {
	    throw new 
		SnmpStatusException(SnmpStatusException.snmpRspInconsistentName);
	} catch (ReflectionException r) {
	    throw new 
		SnmpStatusException(SnmpStatusException.snmpRspInconsistentName);
	} catch (MBeanException m) {
	    Exception t = m.getTargetException();
	    if (t instanceof SnmpStatusException) 
		throw (SnmpStatusException) t;
	    throw new 
		SnmpStatusException(SnmpStatusException.noAccess);
	} catch (Exception e) {
	    throw new 
		SnmpStatusException(SnmpStatusException.noAccess);
	}

	return meta.buildSnmpValue(id,result);
    }

    /**
     * Checks whether an SNMP SET request can be successfully performed. 
     *
     * <p>
     * For each variable in the subrequest, this method calls 
     * checkSetAccess() on the meta object, and then tries to invoke the
     * check<i>AttributeName</i>() method on the MBean. If this method
     * is not defined then it is assumed that the SET won't fail.
     * </p>
     *
     * <p><b><i>
     * This method is called internally by <code>mibgen</code> generated
     * objects and you should never need to call it directly.
     * </i></b></p>
     *
     * @param meta  The metadata object impacted by the subrequest
     * @param name  The ObjectName of the MBean impacted by this subrequest
     * @param req   The SNMP subrequest to execute on the MBean
     * @param depth The depth of the SNMP object in the OID tree.
     *
     * @exception SnmpStatusException if the requested SET operation must
     *      be rejected. Raising an exception will abort the request. <br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerCheckException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     *
     **/            
    public void check(SnmpGenericMetaServer meta, ObjectName name, 
		      SnmpMibSubRequest req, int depth) 
	throws SnmpStatusException {

	final Object data = req.getUserData();

	for (Enumeration e=req.getElements(); e.hasMoreElements();) {
            final SnmpVarBind var= (SnmpVarBind) e.nextElement(); 
	    try {
		final long id = var.oid.getOidArc(depth);
		// call meta.check() here, and meta.check will call check()
		check(meta,name,var.value,id,data);
            } catch(SnmpStatusException x) {
		req.registerCheckException(var,x);
	    }
	}
    }

    /**
     * Checks whether a SET operation can be performed on a given SNMP
     * variable.
     *
     * @param meta  The impacted metadata object
     * @param name  The ObjectName of the impacted MBean
     * @param x     The new requested SnmpValue
     * @param id    The OID arc identifying the variable we're trying to set.
     * @param data  User contextual data allocated through the
     *        {@link com.sun.jmx.snmp.agent.SnmpUserDataFactory}
     *
     * <p>
     * This method calls checkSetAccess() on the meta object, and then 
     * tries to invoke the check<i>AttributeName</i>() method on the MBean. 
     * If this method is not defined then it is assumed that the SET 
     * won't fail.
     * </p>
     *
     * <p><b><i>
     * This method is called internally by <code>mibgen</code> generated
     * objects and you should never need to call it directly.
     * </i></b></p>
     *
     * @exception SnmpStatusException if the requested SET operation must
     *      be rejected. Raising an exception will abort the request. <br>
     *      Exceptions should never be raised directly, but only by means of 
     * <code>
     * req.registerCheckException(<i>VariableId</i>,<i>SnmpStatusException</i>)
     * </code>
     *
     **/
    // XXX xxx ZZZ zzz Maybe we should go through the MBeanInfo here?
    public void check(SnmpGenericMetaServer meta, ObjectName name, 
		      SnmpValue x, long id, Object data) 
	throws SnmpStatusException {
	
	meta.checkSetAccess(x,id,data);
	try {
	    final String attname = meta.getAttributeName(id);
	    final Object attvalue= meta.buildAttributeValue(id,x);
	    final  Object[] params = new Object[1];
	    final  String[] signature = new String[1];

	    params[0]    = attvalue;
	    signature[0] = attvalue.getClass().getName();
	    server.invoke(name,"check"+attname,params,signature);

	} catch( SnmpStatusException e) {
	    throw e;  
	} 
	catch (InstanceNotFoundException i) {
	    throw new 
		SnmpStatusException(SnmpStatusException.snmpRspInconsistentName);
	} catch (ReflectionException r) {
	    // checkXXXX() not defined => do nothing
	} catch (MBeanException m) {
	    Exception t = m.getTargetException();
	    if (t instanceof SnmpStatusException) 
		throw (SnmpStatusException) t;
	    throw new SnmpStatusException(SnmpStatusException.noAccess);
	} catch (Exception e) {
	    throw new 
		SnmpStatusException(SnmpStatusException.noAccess);
	}
    }

    public void registerTableEntry(SnmpMibTable meta, SnmpOid rowOid,
				   ObjectName objname, Object entry) 
	throws SnmpStatusException {
        if (objname == null)
           throw new 
	     SnmpStatusException(SnmpStatusException.snmpRspInconsistentName);
        try  {
            if (entry != null && !server.isRegistered(objname)) 
                server.registerMBean(entry, objname);
	} catch (InstanceAlreadyExistsException e) {
            throw new 
	      SnmpStatusException(SnmpStatusException.snmpRspInconsistentName);
	} catch (MBeanRegistrationException e) {
            throw new SnmpStatusException(SnmpStatusException.snmpRspNoAccess);
	} catch (NotCompliantMBeanException e) {
            throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
	} catch (RuntimeOperationsException e) {
            throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
        } catch(Exception e) {
            throw new SnmpStatusException(SnmpStatusException.snmpRspGenErr);
        }
    }

}
